架構:
package
使用JSONPlaceholder測試:
JSONPlaceholder
使用quicktype將json檔案轉承我們要的形式:
quicktype
測試使用posts:
在models裡新增post_model:
import 'dart:convert';
class PostModel {
PostModel({
this.userId,
this.id,
this.title,
this.body,
});
int userId;
int id;
String title;
String body;
factory PostModel.fromJson(String str) => PostModel.fromMap(json.decode(str));
String toJson() => json.encode(toMap());
factory PostModel.fromMap(Map<String, dynamic> json) => PostModel(
userId: json["userId"] == null ? null : json["userId"],
id: json["id"] == null ? null : json["id"],
title: json["title"] == null ? null : json["title"],
body: json["body"] == null ? null : json["body"],
);
Map<String, dynamic> toMap() => {
"userId": userId == null ? null : userId,
"id": id == null ? null : id,
"title": title == null ? null : title,
"body": body == null ? null : body,
};
}
稍微修改(with null-safety):
import 'dart:convert';
class PostModel {
PostModel({
this.userId,
this.id,
this.title,
this.body,
});
int? userId;
int? id;
String? title;
String? body;
factory PostModel.fromJson(String str) => PostModel.fromMap(json.decode(str));
String toJson() => json.encode(toMap());
factory PostModel.fromMap(Map<String, dynamic> json) => PostModel(
userId: json["userId"] ?? json["userId"],
id: json["id"] ?? json["id"],
title: json["title"] ?? json["title"],
body: json["body"] ?? json["body"],
);
Map<String, dynamic> toMap() => {
"userId": userId ?? userId,
"id": id ?? id,
"title": title ?? title,
"body": body ?? body,
};
}
models.dart:
export './post_model.dart';
在services裡新增post_service:
import 'dart:convert';
import 'dart:developer';
import 'package:flutter_restful/core/models/models.dart';
import 'package:http/http.dart' as http;
class PostService {
Future<PostModel?> findById(String id) async {
PostModel post;
try {
var headers = {'content-type': 'application/json'};
var url = Uri.parse('https://jsonplaceholder.typicode.com/posts/$id');
final response = await http.get(url, headers: headers);
if (response.statusCode == 200) {
post = PostModel.fromJson(response.body);
return post;
} else {
log('請求失敗');
}
} catch (err) {
log('post fetch and set catch error', error: err);
}
}
Future<List<PostModel>> findAll() async {
try {
var headers = {'content-type': 'application/json'};
var url = Uri.parse('https://jsonplaceholder.typicode.com/posts');
final response = await http.get(url, headers: headers);
final extractedData = json.decode(response.body);
final List<PostModel> loadedposts = [];
for (var data in extractedData) {
loadedposts.add(PostModel(
id: data['id'],
userId: data['userId'],
title: data['title'],
body: data['body'],
));
}
return loadedposts;
} catch (err) {
log('posts fetch and set catch error', error: err);
return [];
}
}
Future<PostModel> create(PostModel post, [String token = '']) async {
try {
var headers = {
'content-type': 'application/json',
'Authorization': token,
};
var url = Uri.parse('https://jsonplaceholder.typicode.com/posts');
final response = await http.post(
url,
headers: headers,
body: json.encode({
'id': post.id,
'userId': post.userId,
'title': post.title,
'body': post.body,
}),
);
log(response.body);
return PostModel.fromJson(response.body);
} catch (err) {
log('Add error, ', error: err);
throw Exception(err);
}
}
Future<PostModel> delete(String postId, [String token = '']) async {
try {
var headers = {
'content-type': 'application/json',
'Authorization': token,
};
var url = Uri.parse('https://jsonplaceholder.typicode.com/posts/$postId');
final response = await http.delete(
url,
headers: headers,
);
log(response.body);
if (response.statusCode == 200) {
return PostModel.fromJson(response.body);
} else {
throw Exception('Failed to delete post');
}
} catch (err) {
log('Delete error, ', error: err);
throw Exception(err);
}
}
Future<PostModel> edit(String postId, PostModel post,
[String token = '']) async {
try {
var headers = {
'content-type': 'application/json; charset=UTF-8',
'Authorization': token,
};
var url = Uri.parse('https://jsonplaceholder.typicode.com/posts/$postId');
final response = await http.put(
url,
headers: headers,
body: json.encode({
'id': post.id,
'userId': post.userId,
'title': post.title,
'body': post.body,
}),
// body: post.toJson()
);
log(response.body);
if (response.statusCode == 200) {
return PostModel.fromJson(response.body);
} else {
throw Exception('Failed to edit post');
}
} catch (err) {
log('Edit error, ', error: err);
throw Exception(err);
}
}
}
services.dart:
export './post_service.dart';
home_screen.dart:
import 'package:flutter/material.dart';
import 'package:flutter_restful/core/models/models.dart';
import 'package:flutter_restful/core/services/post_service.dart';
import 'package:flutter_restful/ui/post_detail_screen.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final _formKey = GlobalKey<FormState>();
final TextEditingController _titleController = TextEditingController();
final TextEditingController _contentController = TextEditingController();
PostService post = PostService();
PostModel model = PostModel();
List<PostModel> _posts = [];
@override
void initState() {
// TODO: implement initState
super.initState();
post.findAll().then((value) {
setState(() {
_posts = value;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: _posts.length,
itemBuilder: (context, index) {
PostModel _post = _posts[index];
return ListTile(
title: Text(_post.title ?? ''),
dense: true,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PostDetailScreen(
post: _post,
)));
},
trailing: Wrap(
spacing: 12, // space between two icons
children: <Widget>[
IconButton(
onPressed: () {
post.delete(index.toString());
setState(() {
_posts.removeAt(index);
});
},
icon: const Icon(Icons.delete),
),
IconButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Stack(
clipBehavior: Clip.none,
children: <Widget>[
Positioned(
right: -40.0,
top: -40.0,
child: InkResponse(
onTap: () {
Navigator.of(context).pop();
},
child: const CircleAvatar(
child: Icon(Icons.close),
backgroundColor: Colors.red,
),
),
),
Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Edit Post'),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
decoration: const InputDecoration(
labelText: "title",
),
initialValue: _posts[index].title,
onSaved: (value) {
model.title = value;
},
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
decoration: const InputDecoration(
labelText: "content",
),
initialValue: _posts[index].body,
onSaved: (value) {
model.body = value;
},
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
child: const Text("Edit"),
onPressed: () {
if (_formKey.currentState!
.validate()) {
_formKey.currentState!.save();
model.userId = 1;
model.id = _posts[index].id;
post
.edit(
_posts[index]
.id
.toString(),
model)
.then(
(value) {
setState(() {
_posts[index] = value;
});
},
);
Navigator.of(context).pop();
}
},
),
)
],
),
),
],
),
);
});
},
icon: const Icon(Icons.edit),
),
],
),
);
},
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Stack(
clipBehavior: Clip.none,
children: <Widget>[
Positioned(
right: -40.0,
top: -40.0,
child: InkResponse(
onTap: () {
Navigator.of(context).pop();
},
child: const CircleAvatar(
child: Icon(Icons.close),
backgroundColor: Colors.red,
),
),
),
Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Add Post'),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: _titleController,
decoration: const InputDecoration(
labelText: "title",
),
onSaved: (value) {
model.title = value;
},
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: _contentController,
decoration: const InputDecoration(
labelText: "content",
),
onSaved: (value) {
model.body = value;
},
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
child: const Text("Add"),
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
model.userId = 1;
post.create(model).then(
(value) {
setState(() {
_posts.add(value);
});
},
);
Navigator.of(context).pop();
}
},
),
)
],
),
),
],
),
);
});
},
),
);
}
}
post_detail_screen.dart:
import 'package:flutter/material.dart';
import 'package:flutter_restful/core/models/models.dart';
class PostDetailScreen extends StatefulWidget {
final PostModel post;
const PostDetailScreen({
Key? key,
required this.post,
}) : super(key: key);
@override
_PostDetailScreenState createState() => _PostDetailScreenState();
}
class _PostDetailScreenState extends State<PostDetailScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.post.title ?? ''),
),
body: Column(
children: [
Text(widget.post.body ?? ''),
],
),
);
}
}
main.dart:
import 'package:flutter/material.dart';
import 'ui/home_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomeScreen(),
);
}
}
歡迎大家來我的Blog看:
1.Blog: 文章連結